package evolution.operation.reproduction;

import evolution.individual.Individual;
import evolution.individual.IndividualsFactory;
import evolution.random.RandomGenerator;

public class AMXOReproduction implements ReproductionStrategy {

	private RandomGenerator randomGenerator;
	private IndividualsFactory individualsFactory;
	private double probability = 0.4;

	/**
	 * Arithmetic crossover method (AMXO).
	 */
	@Override
	public Individual[] breed(Individual[] parents) {
		int parentsCount = parents.length;
		int dimensionsCount = parents[0].getChromosome().length;

		Individual[] offspring = new Individual[parentsCount];
		parents = mixUpParentsOrder(parents);

		for (int i = 0; i < parentsCount-1; i = i + 2) {
			Individual parent1 = parents[i];
			Individual parent2 = parents[i + 1];

			if (randomGenerator.nextDouble() < probability) {
				double[] chromosome1 = parent1.getChromosome();
				double[] chromosome2 = parent2.getChromosome();
				double[] descendant1 = new double[dimensionsCount];
				double[] descendant2 = new double[dimensionsCount];
				double a = randomGenerator.nextDouble();
				crossing(chromosome1, chromosome2, descendant1, descendant2, a);

				Individual offspring1 = individualsFactory.create();
				offspring1.setChromosome(descendant1);
				offspring[i] = offspring1;

				Individual offspring2 = individualsFactory.create();
				offspring2.setChromosome(descendant2);
				offspring[i + 1] = offspring2;
			} else {
				offspring[i] = parent1.clone();
				offspring[i + 1] = parent2.clone();
			}
			
			if(parents.length%2 == 1) {
				offspring[parents.length-1] = parents[parents.length-1].clone();
				
			}
		}

		return offspring;
	}

	private void crossing(double[] parent1, double[] parent2,
			double[] descendant1, double[] descendant2, double a) {
		int dimensionsCount = parent1.length;
		double b = 1 - a;

		for (int g = 0; g < dimensionsCount; g++) {
			descendant1[g] = a * parent1[g] + b * parent2[g];
			descendant2[g] = a * parent2[g] + b * parent1[g];
		}
	}

	private Individual[] mixUpParentsOrder(Individual[] parents) {
		int parentsCount = parents.length;
		Individual a;
		Individual b;
		Individual temp;
		int powerOfMixing = (int) parents.length / 2;
		for (int i = 0; i < powerOfMixing; i++) {
			a = parents[randomGenerator.nextInt(parentsCount)];
			b = parents[randomGenerator.nextInt(parentsCount)];
			temp = a;
			a = b;
			b = temp;
		}
		return parents;
	}

	public RandomGenerator getRandomGenerator() {
		return randomGenerator;
	}

	public void setRandomGenerator(RandomGenerator randomGenerator) {
		this.randomGenerator = randomGenerator;
	}

	public IndividualsFactory getIndividualsFactory() {
		return individualsFactory;
	}

	public void setIndividualsFactory(IndividualsFactory individualsFactory) {
		this.individualsFactory = individualsFactory;
	}

	@Override
	public double getProbability() {
		return probability;
	}

	@Override
	public void setProbability(double probability) {
		this.probability = probability;
	}
}
